package sc.common.reflection;

import java.lang.reflect.Field;
import java.util.HashMap;

import org.apache.commons.lang.Validate;
import org.springframework.util.ReflectionUtils;

public class ReflectUtil {

    protected static HashMap<FieldKey, Field> fieldCache;

    static {
        fieldCache = new HashMap<FieldKey, Field>();
    }

    static Field getField(Class<?> cls, String fieldName) {
        Field field = ReflectionUtils.findField(cls, fieldName);
        field.setAccessible(true);
        return field;
    }

    static Object getFieldObject(Field field, Object target) {
        Validate.notNull(field);
        Object obj = ReflectionUtils.getField(field, target);
        return obj;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getFieldValue(Object target, String fieldName, Class<T> returnClass) {
        Class<?> cls = target.getClass();
        FieldKey fieldKey = new FieldKey(cls, fieldName);
        Field field = null;
        if (fieldCache.containsKey(fieldKey)) {
            field = fieldCache.get(fieldKey);
        } else {
            field = getField(cls, fieldName);
            if (field != null) {
                fieldCache.put(fieldKey, field);
            }
        }
        Object obj = getFieldObject(field, target);
        return (T) obj;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getFieldValue(Class<?> cls, String fieldName, Object target, Class<T> returnClass) {
        FieldKey fieldKey = new FieldKey(cls, fieldName);
        Field field = null;
        if (fieldCache.containsKey(fieldKey)) {
            field = fieldCache.get(fieldKey);
        } else {
            field = getField(cls, fieldName);
            if (field != null) {
                fieldCache.put(fieldKey, field);
            }
        }
        Object obj = getFieldObject(field, target);
        return (T) obj;
    }

    public static class FieldKey {

        public FieldKey(Class<?> cls, String fieldName) {
            this.cls = cls;
            this.fieldName = fieldName;
        }

        Class<?> cls;
        String fieldName;

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((cls == null) ? 0 : cls.hashCode());
            result = prime * result + ((fieldName == null) ? 0 : fieldName.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            FieldKey other = (FieldKey) obj;
            if (cls == null) {
                if (other.cls != null) {
                    return false;
                }
            } else if (!cls.equals(other.cls)) {
                return false;
            }
            if (fieldName == null) {
                if (other.fieldName != null) {
                    return false;
                }
            } else if (!fieldName.equals(other.fieldName)) {
                return false;
            }
            return true;
        }
    }
}
